/*
 * This file is part of Euphrat.
 *
 * Copyright © 2012 Bart Verhoeven
 *
 * Euphrat is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Euphrat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Euphrat. If not, see <http://www.gnu.org/licenses/>.
 */
package org.euphrat.client.view.tab;

import static com.google.gwt.user.client.DOM.setStyleAttribute;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

import java.util.Collection;
import java.util.List;

/**
 * <p>A panel that represents a tabbed list of pages. The top of the panel
 * consists of a {@link TabMenu}. As the user clicks on one of the tab in the
 * menu, its {@link Tab#getDescription() description} and {@link Tab#asWidget()
 * widget} are shown right below the menu.</p>
 *
 * <p>The following CSS rules are applied:
 * <ul>
 *   <li>{@code .tabPanel} (tab panel itself)</li>
 *   <li>{@code .tabPanel-description} (tab description)</li>
 * </ul>
 * </p>
 *
 * @author Bart Verhoeven
 */
public class TabPanel extends Composite {

  /** Main style of this tab panel. */
  private static final String TAB_PANEL_STYLE = "tabPanel";
  /** Style of the tab description. */
  private static final String TAB_DESCRIPTION_STYLE = "tabPanel-description";
  /** Name of the background color style attribute. */
  private static final String BACKGROUND_COLOR = "backgroundColor";

  /** Horizontal tab menu panel. */
  private final TabMenu tabMenu = new TabMenu();
  /** Place holder for the tab description. */
  private final HTML description = new HTML();
  /** Place holder for the tab widget. */
  private final VerticalPanel widget = new VerticalPanel();

  /**
   * Creates a new tab panel.
   */
  public TabPanel() {
    // Add components to the main panel.
    FlowPanel mainPanel = new FlowPanel();
    mainPanel.add(tabMenu);
    mainPanel.add(description);
    mainPanel.add(widget);

    // Style components.
    mainPanel.setStyleName(TAB_PANEL_STYLE);
    description.setStyleName(TAB_DESCRIPTION_STYLE);

    // Initialize history behavior.
    History.addValueChangeHandler(new HistoryChangedListener());

    // Initialize the panel widget.
    initWidget(mainPanel);
  }

  /**
   * Returns the component that holds the tab menu.
   *
   * @return the tab menu component
   */
  public Widget getTabMenu() {
    return tabMenu;
  }

  /**
   * Returns the widget that holds the tab description.
   *
   * @return the tab description widget
   */
  public Widget getTabDescription() {
    return description;
  }

  /**
   * Returns the component that holds the tab widget.
   *
   * @return the tab widget component
   */
  public Widget getTabWidget() {
    return widget;
  }

  /**
   * Adds a tab to this panel, if no tab with such name exists.
   *
   * @param tab the tab to add
   * @return true if the panel changed as a result of this call
   */
  public boolean addTab(Tab tab) {
    // Do not add more than one tab with the same name.
    if (tab == null || findTab(tab.getName()) != -1) {
      return false;
    }

    tabMenu.addTab(tab);
    return true;
  }

  /**
   * Adds several tab to this panel, if no tab with such name exists.
   *
   * @param tabs the tab to add
   * @see #addTab(Tab)
   */
  public void addTabs(Collection<? extends Tab> tabs) {
    // Just re-use adding a single tab.
    for (Tab tab : tabs) {
      addTab(tab);
    }
  }

  /**
   * Removes a tab from this panel. If the selected tab is removed, the
   * previously selected tab will be restored.
   *
   * @param tab the tab to remove
   */
  public void removeTab(Tab tab) {
    tabMenu.removeTab(tab);

    // If selected tab was removed, go back to the previous tab.
    if (tabMenu.getSelectedTab() == tab) {
      History.back();
    }
  }

  /**
   * Removes several tab from his panel. If the selected tab is removed, the
   * previously selected tab will be restored.
   *
   * @param tabs the tab to remove
   * @see #removeTab(Tab)
   */
  public void removeTabs(Collection<? extends Tab> tabs) {
    // Just re-use removing a single tab.
    for (Tab tab : tabs) {
      removeTab(tab);
    }
  }

  /**
   * Selects the tab with the given name, if it exists.
   *
   * @param name the name of the tab to select
   */
  public void selectTab(String name) {
    int index = findTab(name);
    selectTab(index);
  }

  /**
   * Selects a tab with the given index, if it exists.
   *
   * @param index the index of the tab to select
   */
  public void selectTab(int index) {
    // There are no tab with a negative index.
    if (index < 0) {
      return;
    }

    Tab selectedTab = tabMenu.getSelectedTab();
    Tab tab = tabMenu.getTab(index);

    // Don't bother displaying an already selected or non-existing tab.
    if (tab == null || tab == selectedTab) {
      return;
    }

    // Deselect old tab.
    if (selectedTab != null) {
      selectedTab.onHide();
      description.setHTML("");
      widget.clear();
    }

    // Select tab.
    tabMenu.selectTab(index);

    // Display tab.
    displayTab(tab);
  }

  /**
   * Displays the given tab.
   *
   * @param tab the tab to display
   */
  private void displayTab(Tab tab) {
    // Update the new tab description.
    SafeHtmlBuilder builder = new SafeHtmlBuilder();
    builder.appendEscapedLines(tab.getDescription());
    description.setHTML(builder.toSafeHtml());

    // Update the new background color.
    String bgColor = tab.getBackgroundColor();
    setStyleAttribute(description.getElement(), BACKGROUND_COLOR, bgColor);

    // Display the new tab widget.
    Widget tabWidget = tab.asWidget();
    tabWidget.setVisible(false);
    widget.add(tabWidget);
    tabWidget.setVisible(true);
    tab.onShow();
  }

  /**
   * Returns the index of the tab with given name, or -1 if no such tab exists.
   *
   * @param name the tab name to find
   * @return index of the tab with given name, or -1
   */
  private int findTab(String name) {
    List<Tab> tabs = tabMenu.getTabs();
    for (int i = 0; i < tabs.size(); i++) {
      String tabName = tabs.get(i).getName();
      if (tabName.equals(name)) {
        return i;
      }
    }
    return -1;
  }

  /**
   * Listener that updates the selected tab when the browser's history changes.
   */
  private class HistoryChangedListener implements ValueChangeHandler<String> {
    @Override
    public void onValueChange(ValueChangeEvent<String> aChangeEvent) {
      // Update tab selection.
      String tabName = aChangeEvent.getValue();
      if (tabName.isEmpty()) {
        selectTab(0);
      }
      else {
        selectTab(tabName);
      }
    }
  }
}
